/**
 * Created by cqb32_000 on 2016-12-20.
 */

var isat = require(__dirname+"/isat");
var sgp4 = isat.sgp4;
var twoline2rv = isat.twoline2rv;
var Cesium = require("cesium");

module.exports = {
    getSatrecsFromTLE: function(tle){
        var satData,satrec;
        var WHICHCONST        = 84;
        var TYPERUN           = 'm';
        var TYPEINPUT         = 'n';
        var rets = twoline2rv(WHICHCONST,
            tle[1],
            tle[2],
            TYPERUN,
            TYPEINPUT);
        satData = {
            name:      tle[0].trim(), // Name: (ISS (ZARYA))
            intlDesig: tle[1].slice(9, 17),
            noradId:   tle[2].split(' ')[1], // NORAD ID (25544)
            tle0: tle[0],
            tle1: tle[1],
            tle2: tle[2]
        };

        satrec   = rets.shift();
        return {satData: satData, satrec: satrec};
    },
    /**
     * 计算某时刻卫星的位置
     */
    getPositionByTime: function(sat, current){
        var jdSat = new Cesium.JulianDate(sat.satrec.jdsatepoch, 0);
        var minutesSinceEpoch = Cesium.JulianDate.secondsDifference(current, jdSat) / Cesium.TimeConstants.SECONDS_PER_MINUTE;
        var rets = sgp4(sat.satrec, minutesSinceEpoch);
        sat.satrec = rets.shift();
        var r = rets.shift();
        var v = rets.shift();

        var scale = 1000;
        var position = new Cesium.Cartesian3(r[0]*scale, r[1]*scale, r[2]*scale);
        var modelMatrix = Cesium.Matrix4.fromRotationTranslation(Cesium.Transforms.computeTemeToPseudoFixedMatrix(current),
            Cesium.Cartesian3.ZERO);

        var pos = Cesium.Matrix4.multiplyByPoint(modelMatrix, position, new Cesium.Cartesian3());
        pos.time = Cesium.JulianDate.clone(current, new Cesium.JulianDate());
        // var obset = this.calculateAzEL(pos);
        // pos.azm = obset.azm;
        // pos.ele = obset.ele;
        var vel = new Cesium.Cartesian3(v[0], v[1], v[2]);
        vel = Cesium.Matrix4.multiplyByPoint(modelMatrix, vel, new Cesium.Cartesian3());
        pos.vel = vel;

        return pos;
    },

    /**
     * 计算某时刻卫星的位置
     */
    getPositionByTimeWithoutTransform: function(sat, current){
        var jdSat = new Cesium.JulianDate(sat.satrec.jdsatepoch, 0);
        var minutesSinceEpoch = Cesium.JulianDate.secondsDifference(current, jdSat) / Cesium.TimeConstants.SECONDS_PER_MINUTE;;
        var rets = sgp4(sat.satrec, minutesSinceEpoch);
        sat.satrec = rets.shift();
        var r = rets.shift();
        var v = rets.shift();

        var scale = 1000;
        var pos = new Cesium.Cartesian3(r[0]*scale, r[1]*scale, r[2]*scale);
        pos.time = Cesium.JulianDate.clone(current, new Cesium.JulianDate());
        // var obset = this.calculateAzEL(pos);
        // pos.azm = obset.azm;
        // pos.ele = obset.ele;
        var vel = new Cesium.Cartesian3(v[0], v[1], v[2]);
        pos.vel = vel;
        return pos;
    },

    getTripeLeftRightPos: function(sat, pos, tilt){
        tilt = tilt || 0;
        var axis = Cesium.Cartesian3.normalize(pos.vel, new Cesium.Cartesian3());

        var orign_angle = this.getSatOrignAngle(sat, pos) / 2;

        var R = 6378137;
        var height = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos).height;

        var H = R + height;
        var left_angle = Math.PI * (orign_angle+tilt) / 180;
        var right_angle = Math.PI * (orign_angle - tilt) / 180;

        var beta_l = Math.asin(H / R * Math.sin(left_angle));
        var theta_left = beta_l - left_angle;
        var beta_r = Math.asin(H / R * Math.sin(Math.abs(right_angle)));
        var theta_right = beta_r - Math.abs(right_angle);

        theta_left = -theta_left;
        theta_right = right_angle < 0 ? -theta_right: theta_right;

        var lqu = Cesium.Quaternion.fromAxisAngle(axis, theta_left);
        var rqu = Cesium.Quaternion.fromAxisAngle(axis, theta_right);

        var leftp = this.applyQuaternion(pos, lqu);
        leftp = this.getSurfacePosition(leftp);
        leftp.time = pos.time;
        leftp.vel = pos.vel;
        var rightp = this.applyQuaternion(pos, rqu);
        rightp = this.getSurfacePosition(rightp);
        rightp.time = pos.time;
        rightp.vel = pos.vel;

        return [leftp, rightp];
    },

    getSatOrignAngle: function(sat, pos){
        var lonlat = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos);
        var height = lonlat.height;
        var fk = sat.sen_width, R = 6378137;
        var theta = fk/R;

        var d = Math.sqrt(R*R*2 - 2*R*R*Math.cos(theta));

        var x = Math.pow(Math.tan(d/R/2),2);
        var a = x/(1+x);

        var dlng = Math.asin(Math.sqrt(a) / Math.cos(lonlat.latitude)) * 2;
        var lon = lonlat.longitude - dlng;

        var newLonLat = Cesium.Cartographic.fromRadians(lon, lonlat.latitude, 0);

        var pos_ground = Cesium.Ellipsoid.WGS84.cartographicToCartesian(newLonLat);

        var dd = Cesium.Cartesian3.distance(pos, pos_ground);
        var gama = Math.acos((height*height + dd*dd - d*d )/2/height/dd);

        return gama * 180 / Math.PI;
    },

    getMaxTiltPositions: function(sat, pos, tilt){
        var axis = Cesium.Cartesian3.normalize(pos.vel, new Cesium.Cartesian3());

        var orign_angle = this.getSatOrignAngle(sat, pos) / 2;

        var R = 6378137;
        var height = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos).height;

        var H = R + height;
        var left_angle = Math.PI * (orign_angle + sat.sen_tilt) / 180;
        var right_angle = Math.PI * (orign_angle + sat.sen_tilt) / 180;

        var beta_l = Math.asin(H / R * Math.sin(left_angle));
        var theta_left = beta_l - left_angle;
        var beta_r = Math.asin(H / R * Math.sin(Math.abs(right_angle)));
        var theta_right = beta_r - Math.abs(right_angle);

        theta_left = -theta_left;


        var lqu = Cesium.Quaternion.fromAxisAngle(axis, theta_left);
        var rqu = Cesium.Quaternion.fromAxisAngle(axis, theta_right);

        var leftp = this.applyQuaternion(pos, lqu);
        leftp = this.getSurfacePosition(leftp);
        leftp.time = pos.time;
        leftp.vel = pos.vel;
        var rightp = this.applyQuaternion(pos, rqu);
        rightp = this.getSurfacePosition(rightp);
        rightp.time = pos.time;
        rightp.vel = pos.vel;

        return [leftp, rightp];
    },

    getOrbitnum: function(sat, time){
        var xno = sat.satData.tle2.substring(52, 63),
            xmnpda = 1.44E3;
        xno = xno * Math.PI * 2 / xmnpda;
        var tempnum = 1.0e-5 * sat.satData.tle1.substring(53, 59);
        var bstar = tempnum / Math.pow(10.0, sat.satData.tle1.charAt(60));
        var xmo = sat.satData.tle2.substring(43, 51);
        xmo = xmo * Math.PI/180;
        var orbitnum = parseInt(sat.satData.tle2.substring(63, 68));
        var jdSat = new Cesium.JulianDate(sat.satrec.jdsatepoch,0);
        var age = Cesium.JulianDate.daysDifference(time, jdSat);

        var ret = Math.floor((xno * xmnpda / Math.PI/2 + age * bstar * 1.0) * age + xmo / Math.PI/2) + parseInt(orbitnum)-1;
        return ret;
    },

    getOrbitPass: function(date, sat){
        var orbitnum = this.getOrbitnum(sat, date);
        var thisOrbit = orbitnum;
        var julianDate = date.clone();
        while (thisOrbit === orbitnum) {
            julianDate = Cesium.JulianDate.addMinutes(julianDate, -1/Cesium.TimeConstants.SECONDS_PER_MINUTE, new Cesium.JulianDate());
            orbitnum = this.getOrbitnum(sat, julianDate);
        }
        julianDate = Cesium.JulianDate.addMinutes(julianDate, 1/Cesium.TimeConstants.SECONDS_PER_MINUTE, new Cesium.JulianDate());
        orbitnum = this.getOrbitnum(sat, julianDate);

        var pos = [];
        var minutesPerOrbit = 2 * Math.PI / sat.satrec.no;
        var pointsPerOrbit = 200;
        var step = minutesPerOrbit / pointsPerOrbit;
        while(thisOrbit == orbitnum){
            var p = this.getPositionByTime(sat, julianDate);
            p.orbitnum = orbitnum;
            pos.push(p);
            julianDate = Cesium.JulianDate.addMinutes(julianDate, step, new Cesium.JulianDate());
            orbitnum = this.getOrbitnum(sat, julianDate);
        }
        return pos;
    },

    getSufaceOrbitPass: function(date, sat){
        var pos = this.getOrbitPassByDate(date, sat);
        pos = pos.map(function(p){
            return this.getSurfacePosition(p);
        });
        return pos;
    },
    /**
     * 获取条带
     * @param date
     * @param sat
     * @returns {Array}
     */
    getStripePass: function(date, sat){
        var pos = this.getOrbitPass(date, sat);
        var leftpos = [], rightpos = [];
        pos.forEach(function(p){
            var lrpos = this.getTripeLeftRightPos(sat, p);
            leftpos.push(lrpos[0]);
            rightpos.push(lrpos[1]);
        });
        var pass = leftpos.concat(rightpos.reverse());
        pass.push(leftpos[0]);
        return pass;
    },

    getOrbitPassWithoutTransform: function(date, sat){
        var orbitnum = getOrbitnum(sat, date);
        var thisOrbit = orbitnum;
        var julianDate = date.clone();
        while (thisOrbit === orbitnum) {
            julianDate = Cesium.JulianDate.addMinutes(julianDate, -1/Cesium.TimeConstants.SECONDS_PER_MINUTE, new Cesium.JulianDate());
            orbitnum = this.getOrbitnum(sat, julianDate);
        }
        julianDate = Cesium.JulianDate.addMinutes(julianDate, 1/Cesium.TimeConstants.SECONDS_PER_MINUTE, new Cesium.JulianDate());
        orbitnum = this.getOrbitnum(sat, julianDate);

        var pos = [];
        var minutesPerOrbit = 2 * Math.PI / sat.satrec.no;
        var pointsPerOrbit = 200;
        var step = minutesPerOrbit / pointsPerOrbit;
        while(thisOrbit == orbitnum){
            var p = this.getPositionByTimeWithoutTransform(sat, julianDate);
            p.orbitnum = orbitnum;
            pos.push(p);
            julianDate = Cesium.JulianDate.addMinutes(julianDate, step, new Cesium.JulianDate());
            orbitnum = this.getOrbitnum(sat, julianDate);
        }
        return pos;
    },

    /**
     * 计算卫星的方位角和仰角
     */
    calculateAzEL: function(pos){
        var S = new Cesium.Cartesian3(pos.x, pos.y, pos.z);
        //var observer = new AGOBSERVER(AGOBSERVER.types.HOME).init(AGSETTINGS.getObserver());
        var oblon = 116;
        var oblat = 41;
        var obalt = 0;

        var op = Cesium.Cartographic.fromDegrees(oblon, oblat, obalt, new Cesium.Cartographic());
        var O = Cesium.Ellipsoid.WGS84.cartographicToCartesian(op);

        var R = Cesium.Cartesian3.subtract(S, O, new Cesium.Cartesian3());
        var r = Cesium.Cartesian3.normalize(R, new Cesium.Cartesian3());
        var CL = Math.cos(op.latitude);
        var CO = Math.cos(op.longitude);
        var SO = Math.sin(op.longitude);
        var SL = Math.sin(op.latitude);
        var Ux = CL * CO;
        var Ex = -SO;
        var Nx = -SL * CO;

        var Uy = CL * SO;
        var Ey = CO;
        var Ny = -SL * SO;

        var Uz = SL;
        var Ez = 0;
        var Nz = CL;

        var u = Cesium.Cartesian3.normalize(new Cesium.Cartesian3(Ux, Uy, Uz), new Cesium.Cartesian3());
        var e = Cesium.Cartesian3.normalize(new Cesium.Cartesian3(Ex, Ey, Ez), new Cesium.Cartesian3());
        var n = Cesium.Cartesian3.normalize(new Cesium.Cartesian3(Nx, Ny, Nz), new Cesium.Cartesian3());
        var el = Cesium.Cartesian3.dot(r, u, new Cesium.Cartesian3());
        var ele = Cesium.Math.toDegrees(Math.asin(el));

        var re = Cesium.Cartesian3.dot(r, e, new Cesium.Cartesian3());
        var rn = Cesium.Cartesian3.dot(r, n, new Cesium.Cartesian3());
        var azm = Cesium.Math.toDegrees(Math.atan(re/rn));
        if(rn < 0){
            azm += 180;
        }
        if(azm < 0){
            azm = azm + 360;
        }

        var ret = {ele: ele, azm: azm};
        return ret;
    },

    /**
     * 计算太阳方位角和高度角
     * @param pos
     * @param date
     */
    calculateSolorAzEL: function(pos, date){
        var pi = Math.PI;
        var Y = date.getMonth()+1;
        var R = date.getDate();
        var NF = date.getFullYear();
        var JD = Cesium.Math.toDegrees(pos.longitude);
        var WD = pos.latitude;
        var JF = 0;
        var S = date.getHours();
        var F = date.getMinutes()+date.getSeconds()/60;
        var AU = 1.49597870691E8;


        var A = NF % 4;
        var C = 32.8;
        if(Y <= 2){
            C = 30.6;
        }
        if(A == 0 && Y > 2){
            C=31.8;
        }
        var G = parseInt(30.6*Y - C + 0.5) + R;
        var L =(JD + JF / 60) / 15;
        var H = S - 8 + F / 60;
        var N = G + (H - L) / 24;

        var N0 = 79.6764 + 0.2422 * (NF - 1985) - parseInt((NF - 1985) / 4);
        var t = N - N0;
        var theta = 2*pi*t / 365.2422;

        var sintheta = Math.sin(theta);
        var sin2theta = Math.sin(theta*2);
        var sin3theta = Math.sin(theta*3);
        var costheta = Math.cos(theta);
        var cos2theta = Math.cos(theta*2);
        var cos3theta = Math.cos(theta*3);

        //日地距离
        var ER = 1.000423 + 0.032359 * sintheta + 0.000086 * sin2theta - 0.008349 * costheta + 0.000115 * cos2theta;
        var R = Math.sqrt(ER) * AU;
        //太阳赤纬角
        var ED = 0.3723 + 23.2567 * sintheta + 0.1149 * sin2theta - 0.1712 * sin3theta - 0.758 * costheta + 0.3656 * cos2theta + 0.0201 * cos3theta;
        var Ed = ED * pi / 180;

        var Et = 0.0028 - 1.9857 * sintheta + 9.9059 * sin2theta - 7.0924 * costheta - 0.6882 * cos2theta;

        //当地时间
        var Sd = S + (F - (120 - (JD + JF/60))*4)/60;

        //真太阳时
        var ST = Sd + Et/60;
        //太阳时角
        var W = (ST - 12) * 15 * pi / 180;
        var w = (ST - 12) * 15;

        var sinh = Math.sin(Ed) * Math.sin(WD) + Math.cos(Ed)*Math.cos(WD)*Math.cos(W);

        var h0 = Math.asin(sinh);
        var H = Math.asin(sinh) * 180 / pi;

        var	cosA =(Math.sin(h0) * Math.sin(WD) - Math.sin(Ed)) / (Math.cos(h0) *  Math.cos(WD));
        var A = Math.acos(cosA) * 180 / pi;
        if(w > 0){
            A = 180 + A;
        }else{
            if(ST < 0){
                A = 180 + A;
            }else{
                A = 180 - A;
            }
        }

        return {
            ele: H,
            azm: A,
            dis: R,
            ed: ED
        };
    },

    getPathByTime: function(sat, start, end, stepseconds){
        stepseconds = stepseconds || 3.31;
        var allminutes = Cesium.JulianDate.secondsDifference(end, start) / Cesium.TimeConstants.SECONDS_PER_MINUTE;
        var julianDate,minutes;
        var step = stepseconds/Cesium.TimeConstants.SECONDS_PER_MINUTE;

        var positions = [];
        for(minutes = 0; minutes<= allminutes;){
            julianDate = Cesium.JulianDate.addMinutes(start, minutes, new Cesium.JulianDate());
            var pos = this.getPositionByTime(sat, julianDate);
            positions.push(pos);
            minutes += step;
        }
        if(minutes > allminutes){
            var pos = this.getPositionByTime(sat, end);
            positions.push(pos);
        }

        return positions;
    },

    getSurfacePosition: function(position){
        var pp = Cesium.Ellipsoid.WGS84.cartesianToCartographic(position);
        pp.height = 0;
        var p = Cesium.Ellipsoid.WGS84.cartographicToCartesian(pp);
        return p;
    },

    getStripeByTime: function(sat, tilt, start, end, stepseconds){
        stepseconds = stepseconds || 3.31;
        var allminutes = Cesium.JulianDate.secondsDifference(end, start) / Cesium.TimeConstants.SECONDS_PER_MINUTE;
        var julianDate = new Cesium.JulianDate(),minutes;
        var step = stepseconds/Cesium.TimeConstants.SECONDS_PER_MINUTE;

        var positions = [];
        var lfpos = [],rfpos = [];
        for(minutes = 0; minutes<= allminutes;){
            Cesium.JulianDate.addMinutes(start, minutes, julianDate);
            var pos = this.getPositionByTime(sat, julianDate);
            var lrpos = this.getTripeLeftRightPos(sat, pos, tilt);

            lfpos.push(lrpos[0]);
            rfpos.push(lrpos[1]);
            minutes += step;
        }
        if(minutes > allminutes){
            var pos = this.getPositionByTime(sat, end);
            var lrpos = this.getTripeLeftRightPos(sat, pos, tilt);
            lfpos.push(lrpos[0]);
            rfpos.push(lrpos[1]);
        }
        positions = lfpos.concat(rfpos.reverse());

        return positions;
    },

    getTiltStripeByTime: function(sat, start, end, stepseconds){
        stepseconds = stepseconds || 3.31;
        var allminutes = Cesium.JulianDate.secondsDifference(end, start) / Cesium.TimeConstants.SECONDS_PER_MINUTE;
        var julianDate,minutes;
        var step = stepseconds/Cesium.TimeConstants.SECONDS_PER_MINUTE;
        var scenenum = Math.floor(allminutes/step);
        var hasmore = (allminutes/step != scenenum) && (allminutes%step > 0.001);

        //var positions = [];
        var polys = [];
        var lfpos = [],rfpos = [];
        for(var i = 0; i < scenenum; i++){
            julianDate = Cesium.JulianDate.addMinutes(start, step*i, new Cesium.JulianDate());
            var pos = this.getPositionByTime(sat, julianDate);
            var nextpos = this.getPositionByTime(sat, Cesium.JulianDate.addMinutes(julianDate, step, new Cesium.JulianDate()));
            var lrpos = this.getMaxTiltPositions(sat, pos);
            var nextlrpos = this.getMaxTiltPositions(sat, nextpos);

            var poly = [lrpos[0], lrpos[1], nextlrpos[1],nextlrpos[0]];
            polys.push(poly);
        }
        if(hasmore){
            julianDate = Cesium.JulianDate.addMinutes(start, step*scenenum, new Cesium.JulianDate());
            var pos = this.getPositionByTime(sat, julianDate);
            var nextpos = this.getPositionByTime(sat, end);
            var lrpos = this.getMaxTiltPositions(sat, pos);
            var nextlrpos = this.getMaxTiltPositions(sat, nextpos);
            var poly = [lrpos[0], lrpos[1], nextlrpos[1],nextlrpos[0]];
            polys.push(poly);
        }

        return polys;
    },

    /**
     * 矢量旋转
     */
    applyQuaternion: function( v, q ) {

        var x = v.x;
        var y = v.y;
        var z = v.z;

        var qx = q.x;
        var qy = q.y;
        var qz = q.z;
        var qw = q.w;

        // calculate quat * vector

        var ix =  qw * x + qy * z - qz * y;
        var iy =  qw * y + qz * x - qx * z;
        var iz =  qw * z + qx * y - qy * x;
        var iw = -qx * x - qy * y - qz * z;

        // calculate result * inverse quat

        var ret = new Cesium.Cartesian3(0,0,0);
        ret.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
        ret.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
        ret.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;

        return ret;
    },

    DayLight: {
        start: "08:00:00",
        end: "17:00:00"
    }
};